# vPresent is an immersive presentation creation and display application.
# Copyright (C) 2007 by Infiscape Corporation
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import os
import types

from PyQt4 import QtCore, QtGui

import imagerefeditui
import gui.util.environment as environ
import opendocument

class ImageRefEdit(QtGui.QDialog, imagerefeditui.Ui_ImageRefEditBase):
   def __init__(self, imageRef, undoStack, parent=None):
      QtGui.QDialog.__init__(self, parent)
      self.setupUi()

      self.mImageRef = imageRef
      self.undoStack = undoStack

      # Get the current document and working directory.
      env = environ.Environment()
      self.curDocument = env.curDocument
      doc = self.curDocument.document
      self.mWorkDir = env.workDir

      # Create a texture model that allows the user to choose a texture.
      self.mTextureModel = TextureModel(doc, self.mWorkDir)
      #self.mTextureView.setMouseTracking(True)
      self.mTextureView.setModel(self.mTextureModel)

      # Keep around the original draw_fill_image_name so that we can restore
      # the state before exiting this dialog. The actual change will happen
      # in the code that creates this dialog.
      self.mStartingDrawImage = self.mImageRef.draw_fill_image_name
      self.mStartingMirror = self.mImageRef.vpresent_mirror

      # Try to select the current texture.
      for i in xrange(self.mTextureModel.rowCount()):
         idx = self.mTextureModel.index(i)
         tex = self.mTextureModel.data(idx, QtCore.Qt.UserRole)
         if tex.draw_name == self.mStartingDrawImage:
            self.mTextureView.selectionModel().blockSignals(True)
            self.mTextureView.setCurrentIndex(idx)
            self.mTextureView.scrollTo(idx)
            self.mTextureView.selectionModel().blockSignals(False)
            break

      self.connect(self.mTextureView.selectionModel(),
                   QtCore.SIGNAL("currentChanged(const QModelIndex&, const QModelIndex&)"),
                   self.onCurrentChanged)

      self.connect(self.mFlipHor, QtCore.SIGNAL("stateChanged(int)"), self.onCheckChanged)
      self.connect(self.mFlipVert, QtCore.SIGNAL("stateChanged(int)"), self.onCheckChanged)

      # Update the dialog state.
      self.__updateState()

   def setupUi(self):
      imagerefeditui.Ui_ImageRefEditBase.setupUi(self, self)
      self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.FramelessWindowHint)
      self.connect(self.mAddTexture, QtCore.SIGNAL("clicked(bool)"), self.onAddTexture)

      # Set the icon size in the texture list view.
      self.mTextureView.setIconSize(QtCore.QSize(64, 64))

      # Give the layout a little help.
      self.mTextureView.setGridSize(QtCore.QSize(75, 75))
      self.mTextureView.setUniformItemSizes(True)

      # We want the icons to go from left to right and wrap to the next row.
      self.mTextureView.setFlow(QtGui.QListView.LeftToRight)
      self.mTextureView.setWrapping(True)
      #self.mTextureView.setViewMode(QtGui.QListView.IconMode)

      # NOTE: It is very important to tell the view to re-layout everytime
      #       it is resized.
      self.mTextureView.setResizeMode(QtGui.QListView.Adjust)

   def __updateState(self):
      widgets = [self.mFlipHor, self.mFlipVert, self.mTextureView.selectionModel()]
      for w in widgets:
         w.blockSignals(True)

      # TODO: Select the correct texture.

      # Update the check boxes.
      if self.mImageRef.vpresent_mirror in ['horizontal', 'both']:
         self.mFlipHor.setCheckState(QtCore.Qt.Checked)
      else:
         self.mFlipHor.setCheckState(QtCore.Qt.Unchecked)

      if self.mImageRef.vpresent_mirror in ['vertical', 'both']:
         self.mFlipVert.setCheckState(QtCore.Qt.Checked)
      else:
         self.mFlipVert.setCheckState(QtCore.Qt.Unchecked)


      for w in widgets:
         w.blockSignals(False)

   def onCurrentChanged(self,current,previous):
      if current.isValid():
         # Update the image ref to point to the current draw:fill-image
         tex = self.mTextureModel.data(current, QtCore.Qt.UserRole)

         set_image_name = ChangeImageRef(self.curDocument.document,
                                         self.mImageRef, tex.draw_name)

         self.undoStack.push(set_image_name)

         # Update the dialog state.
         self.__updateState()

         # Update the OpenSG widget.
         env = environ.Environment()
         env.updateGL()

   def onCheckChanged(self, state):
      mirror = 0
      if QtCore.Qt.Checked == self.mFlipHor.checkState():
         mirror += 1
      if QtCore.Qt.Checked == self.mFlipVert.checkState():
         mirror += 2
      values = ['none', 'horizontal', 'vertical', 'both']

      # Set the new value.
      change_mirror = ChangeMirror(self.mImageRef, values[mirror])
      self.undoStack.push(change_mirror)

      # Update the dialog state.
      self.__updateState()

      # Update the OpenSG widget.
      env = environ.Environment()
      env.updateGL()

   def onAddTexture(self):
      env = environ.Environment()
      image_suffix_str = env.getValidImageExtensions()

      # Open a file dialog to get the texture filename.
      file_names = \
         QtGui.QFileDialog.getOpenFileNames(
            None, self.tr("Add Texture"), env.fileStartDir,
            image_suffix_str
         )

      row = -1

      for file_name in file_names:
         # Convert the filename from a QString into a python str.
         file_name = str(file_name)
         # Keep around the last directory location.
         env.fileStartDir =  os.path.abspath(os.path.dirname(file_name))

         # Import the new texture into the presentation.
         href = env.curDocument.importResource(file_name)

         # Insert the new texture into the document.
         row = self.mTextureModel.rowCount()
         name = self.curDocument.document.generateFillImageName()
         new_fill_image = opendocument.createNode('draw:fill-image',
            draw_name=name, draw_display_name=os.path.basename(file_name),
            xlink_href=href)

         self.mTextureModel.insertTexture(row, new_fill_image)

      # Only select the last texture inserted.
      if row > 0:
         idx = self.mTextureModel.index(row)
         self.mTextureView.selectionModel().setCurrentIndex(idx, QtGui.QItemSelectionModel.ClearAndSelect)

class TextureModel(QtCore.QAbstractListModel):
   def __init__(self, document, workDir, parent=None):
      QtCore.QAbstractListModel.__init__(self, parent)
      self.mDocument = document
      self.mWorkDir = workDir
      self.mPres = self.mDocument.content.find('./office:body/office:presentation')

      self.mStyles = self.mDocument.docStyles.find('./office:styles')

   def addTexture(self, newFillImage):
      self.insertTexture(self.rowCount(), newFillImage)

   def insertTexture(self, row, newFillImage):
      """
      Insert a fill image into the presentation.
      @param row Index into the list of fill images, not presentation children.
      """
      assert row >= 0
      textures = self.mDocument.textures[:]
      # If there are no textures, insert at the beginning.
      if len(textures) == 0:
         idx = 0
      else:
         # If inserting in middle, insert at the position of the previous texture.
         if row < len(textures):
            prev = textures[row]
            idx = self.mStyles._children.index(prev)
         # Append to the position of the last textures.
         else:
            prev = textures[-1]
            idx = self.mStyles._children.index(prev) + 1

      # Update the model with our row, but the presentation with
      # the child index.
      self.beginInsertRows(QtCore.QModelIndex(), row, row)
      # Add new style to document. 
      self.mStyles.insertChild(idx, newFillImage)
      # Add new image to texture db.
      self.mDocument.addFillImage(newFillImage)
      self.endInsertRows()

   def removeTexture(self, rowOrTexture):
      """
      Remove a texture from the presentation.
      @param rowOrTexture Row index or a texture object. If it is a row index
                          then it is an index into the list of textures, not
                          presentation children.
      """
      if rowOrTexture is None:
         return

      # Handle both cases of index and texture object.
      if type(rowOrTexture) == types.IntType:
         row = rowOrTexture
         texture = self.mDocument.textures[row]
      else:
         texture = rowOrTexture
         row = self.mDocument.textures.index(texture)

      # Get the child index for the texture object.
      idx = self.mStyles._children.index(texture)

      # Update the model with our row, but the presentation with
      # the child index.
      self.beginRemoveRows(QtCore.QModelIndex(), row, row)
      self.mStyles.subChild(idx)
      # Remove image from texture db.
      self.mDocument.removeFillImage(texture)
      self.endRemoveRows()

   def flags(self, index):
      default_flags = QtCore.QAbstractListModel.flags(self, index)
      return default_flags

   def data(self, index, role=QtCore.Qt.DisplayRole):
      """ Returns the data representation of each node in the cluster.
      """
      if not index.isValid():
         return QtCore.QVariant()

      # Get the cluster node we want data for.
      tex = self.mDocument.textures[index.row()]

      if role == QtCore.Qt.UserRole:
         return tex

      if tex is not None:
         # Return an icon representing the operating system.
         if role == QtCore.Qt.DecorationRole:
            return QtCore.QVariant(tex._getThumbnail())
         elif role == QtCore.Qt.SizeHintRole:
            return QtCore.QVariant(QtCore.QSize(75, 75))
         # XXX: Don't return FillImage name.
         # Return the name of the node.
         #elif role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
         #   if tex.draw_display_name is None:
         #      return QtCore.QVariant(str(tex.draw_name))
         #   return QtCore.QVariant(str(tex.draw_display_name))
       
      return QtCore.QVariant()

   def setData(self, index, value, role):
      """ Set the name of the tex at the given index. """
      if not index.isValid():
         return False
      if index.row() >= self.rowCount():
         return False
      tex = self.mDocument.textures[index.row()]
      if tex is None:
         return False

      if role == QtCore.Qt.EditRole:
         new_name = unicode(value.toString())
         if tex.draw_display_name != new_name:
            tex.draw_display_name = new_name
      #elif role == QtCore.Qt.DecorationRole:
      #   assert QtGui.QPixmap == type(value)
      #   tex._thumbnailImage = value

      self.emit(QtCore.SIGNAL("dataChanged(const QModelIndex &, constQModelIndex &)"), index, index)

      return True

   def rowCount(self, parent=QtCore.QModelIndex()):
      """ Returns the number of nodes in the current cluster configuration.
      """
      # If the parent is not valid, then we have no children.
      if parent.isValid():
         return 0
      else:
         return len(self.mDocument.textures)


CHANGE_IMAGE_REF_ID = 10
CHANGE_MIRROR_ID    = 20

class ChangeImageRef(QtGui.QUndoCommand):
   def __init__(self, doc, imageRef, name, parent=None):
      QtGui.QUndoCommand.__init__(self, "Image Ref Change", parent)
      self.mDocument = doc
      self.mImageRef = imageRef
      self.mNewName = name
      self.mOldName = self.mImageRef.draw_fill_image_name

   def clone(self):
      cmd = ChangeImageRef(self.mDocument, self.mImageRef, self.mNewName)
      cmd.mOldName = self.mOldName
      return cmd

   def id(self):
      return CHANGE_IMAGE_REF_ID

   def mergeWith(self, command):
      if command.id() == self.id():
         self.mNewName = command.mNewName
         return True
      return False

   def redo(self):
      self.mImageRef.draw_fill_image_name = self.mNewName
      # The UI should not allow us to assign a fill image that does not exist.
      assert self.mDocument.getFillImage(self.mNewName) is not None

      # Update the OpenSG widget.
      env = environ.Environment()
      env.updateGL()

   def undo(self):
      self.mImageRef.draw_fill_image_name = self.mOldName

      # Update the OpenSG widget.
      env = environ.Environment()
      env.updateGL()


class ChangeMirror(QtGui.QUndoCommand):
   def __init__(self, imageRef, value, parent=None):
      QtGui.QUndoCommand.__init__(self, "Mirror Change", parent)
      self.mImageRef = imageRef
      self.mNewValue = value
      self.mOldValue = self.mImageRef.vpresent_mirror

   def clone(self):
      cmd = ChangeMirror(self.mImageRef, self.mNewValue)
      cmd.mOldValue = self.mOldValue
      return cmd

   def id(self):
      return CHANGE_MIRROR_ID

   def mergeWith(self, command):
      if command.id() == self.id():
         self.mNewValue = command.mNewValue
         return True
      return False

   def redo(self):
      self.mImageRef.vpresent_mirror = self.mNewValue

      # Update the OpenSG widget.
      env = environ.Environment()
      env.updateGL()

   def undo(self):
      self.mImageRef.vpresent_mirror = self.mOldValue

      # Update the OpenSG widget.
      env = environ.Environment()
      env.updateGL()


import vpresent_rc

if __name__ == "__main__":
   import sys
   app = QtGui.QApplication(sys.argv)
   dialog = SphereBackground()
   dialog.show()
   sys.exit(app.exec_())
